﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections;
using System.Net.Sockets;
using System.Net;
using System.Drawing;
using System.IO;

namespace TCPSocket_DLL
{
    public enum DATATYPE
    {
        STRING,
        FILE,
        BITMAP
    };
    public class TCPSocket : TCPConnect_I
    {
        EventArgs e = new EventArgs();
        public EventHandler DataReceived;
        public EventHandler Connected;
        public EventHandler ConnectedToClient;
        public EventHandler Disconnected;
        public EventHandler Error;
        EventHandler GetBuffSize;

        ArrayList alSockets;

        Thread thdListener;
        Thread thdHandler;

        int buffLength = 4;

        public int BuffLength
        {
            get { return buffLength; }
            set { buffLength = value; }
        }
        byte[] buffer;

        Socket handlerSocket;
        NetworkStream networkStream;
        TcpListener tcpListener;
        TcpClient tcpClient;

        Boolean WaitForBuffSize = false;
        Boolean end = false;
        Boolean tryToConnectClient = false;
        Boolean isStarted = false;
        public Boolean IsStarted
        {
            get { return isStarted; }
        }

        int port = 0;

        DATATYPE streamType;

        MemoryStream MS;

        public TCPSocket()
        {
            buffer = new byte[buffLength];
            if (tryToConnectClient)
                this.Connected += new EventHandler(ConnectToClient);
        }
        public TCPSocket(int port, DATATYPE DT, Boolean TwoWayConn)
        {
            this.port = port; streamType = DT;

            tryToConnectClient = TwoWayConn;
            if (TwoWayConn)
                this.Connected += new EventHandler(ConnectToClient);
            switch (DT)
            {
                case DATATYPE.FILE: buffLength = 1024;
                    break;
                case DATATYPE.BITMAP: buffLength = 1024;
                    break;
                case DATATYPE.STRING: buffLength = 4;
                    break;
            }
            buffer = new byte[buffLength];
        }


        public void StartListening()
        {
            end = false;
            isStarted = true;
            alSockets = new ArrayList();
            thdListener = new Thread(new ThreadStart(ListenerThread));
            thdListener.Start();
        }
        public void StartHandler()
        {
            end = false;
            isStarted = true;
            ThreadStart thdstHandler = new ThreadStart(HandlerThread);
            thdHandler = new Thread(thdstHandler);
            thdHandler.Start();
        }
        public void Stop() { end = true; isStarted = false; }

        public void ConnectToClient(object sender, EventArgs e)
        {
            if (tcpClient == null || !tcpClient.Connected)
            {
                tcpClient = new TcpClient(handlerSocket.RemoteEndPoint.AddressFamily);
                tcpClient.Connect(((IPEndPoint)handlerSocket.RemoteEndPoint).Address.ToString(), port);
                if (ConnectedToClient != null)
                    ConnectedToClient.Invoke(handlerSocket.RemoteEndPoint, e);
            }
        }
        public void Connect(string host)
        {
            if (port == 0) port = 8090;
            tcpClient = new TcpClient(host, port);
            networkStream = tcpClient.GetStream();
        }
        public void Connect(string host, int port)
        {
            if (port > 0) this.port = port;

            Connect(host);
        }
        public void Connect(string host, int port, Boolean twoWayConn)
        {
            Connect(host, port);
            if (twoWayConn)
                StartHandler();
        }

        public void WaitForBufferSize()
        {
            WaitForBuffSize = true;
            GetBuffSize += new EventHandler(SetBufferSize);
        }
        public void SetBufferSize(object nBuffSize, EventArgs e)
        {
            BuffLength = (int)Math.Pow(2, (int)nBuffSize);
            GetBuffSize = null;
        }

        public void SendBufferSize()
        {
            byte[] a = new byte[1];
            a[0] = (byte)Math.Log(BuffLength, 2);
            networkStream.Write(a, 0, 1);
        }
        public void SendString(string data)
        {
            if (networkStream != null)
            {
                buffer = new byte[data.Length];
                for (int i = 0; i < data.Length; i++)
                    buffer[i] = Convert.ToByte(data[i]);
                lock (this)
                {
                    try
                    {
                        networkStream.Write(buffer, 0, data.Length);
                    }
                    catch (IOException ioe) { return; }
                }
            }
        }

        //long bitmapPos = 0;
        public void SendBitmap(Bitmap bMap)
        {
            using (MemoryStream MS = new MemoryStream())
            {
                if (networkStream != null)
                {
                    bMap.Save(MS, System.Drawing.Imaging.ImageFormat.Jpeg);
                    MS.Position = 0;
                    lock (this)
                    {
                        //bMap.Save(networkStream, System.Drawing.Imaging.ImageFormat.Jpeg);
                        while (MS.Position < MS.Length)
                        {
                            MS.Read(buffer, 0, buffLength);
                            networkStream.Write(buffer, 0, buffLength);
                        }
                    }
                }
            }
        }
        public void SendFile(string filePath) { throw new NotImplementedException(); }

        public void ListenerThread()
        {
            tcpListener = new TcpListener(IPAddress.Any, port);
            tcpListener.Start();
            while (true)
            {
                if (end)
                {
                    Thread.Sleep(100);
                    tcpListener.Stop();

                    if (Disconnected != null)
                        Disconnected.Invoke(
                            (handlerSocket != null ?
                            "Disconnected from: " + handlerSocket.RemoteEndPoint.ToString() :
                            "Stopped"),
                            e);
                    break;
                }
                if (tcpListener.Pending())
                {
                    handlerSocket = tcpListener.AcceptSocket();
                    if (handlerSocket.Connected)
                    {
                        lock (this) alSockets.Add(handlerSocket);
                        ThreadStart thdstHandler = new ThreadStart(HandlerThread);
                        thdHandler = new Thread(thdstHandler);
                        thdHandler.Start();

                        if (Connected != null)
                            Connected.Invoke(handlerSocket.RemoteEndPoint.ToString(), e);
                    }
                }
                else
                    Thread.Sleep(50);
            }
        }

        public void HandlerThread()
        {
            if (networkStream == null)
            {
                handlerSocket = (Socket)alSockets[0];
                networkStream = new NetworkStream(handlerSocket);
            }
            using (MemoryStream MS = new MemoryStream())
            {
                while (true)
                {
                    if (end) break;
                    try
                    {
                        if (networkStream.DataAvailable)
                        {
                            lock (this)
                            {
                                MS.Position = 0;
                                while (networkStream.DataAvailable)
                                {
                                    if (!WaitForBuffSize)
                                    {
                                        switch (streamType)
                                        {
                                            case DATATYPE.BITMAP:
                                            case DATATYPE.FILE:
                                                do
                                                {
                                                    while (networkStream.DataAvailable)
                                                    {
                                                        networkStream.Read(buffer, 0, BuffLength);
                                                        MS.Write(buffer, 0, BuffLength);
                                                    }
                                                    Thread.Sleep(10);
                                                } while (networkStream.DataAvailable);
                                                if (DataReceived != null)
                                                    DataReceived.Invoke(MS, e);
                                                break;
                                            case DATATYPE.STRING:
                                                List<byte> receivedBytes = new List<byte>();
                                                while (networkStream.DataAvailable)
                                                    receivedBytes.Add((byte)networkStream.ReadByte());
                                                byte[] a = new byte[receivedBytes.Count];
                                                a = receivedBytes.ToArray();
                                                if (DataReceived != null)
                                                    DataReceived.Invoke(a, e);
                                                break;
                                        }
                                    }
                                    else
                                        if (GetBuffSize != null)
                                            GetBuffSize.Invoke(networkStream.ReadByte(), e);
                                }
                            }
                        }
                        else Thread.Sleep(5);
                    }
                    catch (Exception ex)
                    {
                        if (Error != null)
                            Error.Invoke(ex.Message, e);
                        break;
                    }
                }
            }
        }
    }
}
